from ctypes import * 
from typing import List, Any
from bcat.error import InvalidArgumentError
from bcat.util.str import Str
from bcat.ffi.loader import SharedObjectsLoader

from abc import ABC, abstractmethod

class CSharedLibrary(ABC):
    def __init__(self, so_name: str | List[str]):
        self.__so_name = so_name
        (self.__loader, self.__target) = self.load()
        self._install()
    
    def load(self) -> (Any, Any):
        loader = SharedObjectsLoader.from_one(self.__so_name).load()
        target = loader.get(self.__so_name)
        return (loader, target)

    def target(self) -> Any:
        return self.__target

    def so_name(self) -> str:
        return self.__so_name

    def declare_c_func(self, func_name: str, args_types: List[Any], result_type: Any | None) -> Any:
        if Str(func_name).is_empty():
            raise InvalidArgumentError("args:function name is Empty", 1)

        c_func = getattr(self.target(), func_name)
        if c_func is not None:
            c_func.argtypes = args_types
            c_func.restype = result_type
            return c_func
        else:
            raise InvalidArgumentError.no_args(f"get [target.{func_name}] failed for so:[{self.so_name()}]")

    @abstractmethod
    def _install(self):
        pass


class VirtioDiskImpl(CSharedLibrary):
    def __init__(self, so_names: str | List[str], fname: str, size: int):
        super().__init__(so_names) 
        self._ptr = self.c_create(fname.encode(), size)

    def _install(self):
        self.c_create = self.declare_c_func("virtio_disk_impl_create", [c_char_p, c_ulong], c_void_p)
        self.c_destroy = self.declare_c_func("virtio_disk_impl_destroy", [c_void_p], None)

        self.c_print = self.declare_c_func("virtio_disk_impl_print", [c_void_p], None)
        self.c_seek = self.declare_c_func("virtio_disk_impl_seek", [c_void_p, c_ulong], c_int);
        self.c_rw = self.declare_c_func("virtio_disk_impl_rw", [c_void_p, c_void_p, c_int, c_int], c_int);
            
    def __del__(self):
        self._destroy()
        self._ptr = None

    def _destroy(self):
        self.c_destroy(self._ptr)

    def print(self):
        self.c_print(self._ptr)

    def seek(self, offset: int) -> int:
        return self.c_seek(self._ptr, offset)

    def read(self, size: int):
        buf = create_string_buffer(size)
        sz = self.c_rw(self._ptr,  buf, size, 0)
        assert( sz >= 0)
        return buf[0:sz]

    def write(self, data: bytes) -> int:
        return self.c_rw(self._ptr, cast(data, c_void_p), len(data), 1)


import unittest 
import os
class TestVirtioDiskImpl(unittest.TestCase):
    def test_main(self):
        impl_path = "../../impl/libimpl.so"
        for i in range(3):
            fname = "a.img"
            v = VirtioDiskImpl(impl_path, fname, 1024)
            v.print()
            v.write(b"h"*512)
            del v
            os.remove(fname)

    def test_write_and_read_block(self):
        impl_path = "../../impl/libimpl.so"
        fname = "test_write.img"
        v = VirtioDiskImpl(impl_path, fname, 1024)
        v.write(b"1"*512)
        v.write(b"2"*512)

        v.seek(0)
        self.assertEqual(b"1"*512, v.read(512))
        self.assertEqual(b"2"*512, v.read(512))
        del v
        os.remove(fname)
        
        
